<?php
/**
 * USPS Rate Calculation Service for shipping
 *
 * @author Pavel Kovalyov
 */
class RCMS_Object_Shipping_Services_USPS implements RCMS_Core_ShippingServiceInterface {

    // Test server:
    //protected $_server = 'http://testing.shippingapis.com/ShippingAPITest.dll';
    // Production server:
    private $_server = 'http://production.shippingapis.com/ShippingAPI.dll';

    private $_userId;
    private $_password;

    private $_destination;
    private $_origination;

    private $_packageWeight;
    
    private $_domesticParams;
    private $_internationalParams;

    private $_error = array ();

    /**
     * @param <array> $config 
     */
    public function  __construct($config = null) {
        if (is_array($config)){
            $this->_userId = $config['userid'];
            $this->_domesticParams = array(
                'serviceType' => $config['service-type'],
                'packageSize' => $config['package-size'],
                'machinable' => $config['machinable']
            );
            $this->_internationalParams = array(
                'machinable' => $config['machinable'],
                'mailtype' => $config['mail-type']
            );
        }
    }

    /**
     * Setter for API server address
     * @param string $server - URL
     */
    public function setServer($server) {
        $this->_server = $server;
    }

    /**
     * Setter for user ID, must be valid to acces the API server
     * @param string $userId
     */
    public function setUserId($userId){
        $this->_userId = $userId;
    }

    /**
     * Setter for password (optional)
     * @param string $password
     */
    public function setPassword($password) {
        $this->_password = $password;
    }

    /**
     * Setter for weight
     * @param array/string $weight  - array('pounds','ounces') or string with weight
     * @param string $unit - weight unit, lbs or Kg
     */
    public function setWeight($weight,$unit = 'lbs') {
        switch  ($unit) {
            case 'lbs':
                if (is_array($weight)){
                    $this->_packageWeight = array(
                        'pounds' => $weight[0],
                        'ounces' => $weight[1]
                    );
                } elseif (is_float($weight)) {
                    $num = explode('.',$weight);
                    $this->_packageWeight = array (
                        'pounds' => (int) $num[0],
                        'ounces' => (float) round(('0.'.$num[1]) * 16)
                    );
                } else {
                    $this->_packageWeight = array(
                        'pounds' => $weight,
                        'ounces' => '0'
                    );
                }
                break;
            case 'Kg':
                if (!is_array($weight)) {
                    $weight = $weight * 2.2;
                    $this->_packageWeight = array(
                        'pounds' => $weight,
                        'ounces' => '0'
                    );
                }
                break;
        }
    }

    /**
     * Setter for originational information
     * @param array  $originationAddress
     * @param string $originationAddress['country'] - Country
     * @param string $originationAddress['zip'] - Zip Code (must be number)
     */
    public function setOrigination(array $originationAddress) {
        $this->_origination = $originationAddress;
    }
    /**
     * Setter for destinational information
     * @param array  $destinationAddress
     * @param string $destinationAddress['country'] - Country
     * @param string $destinationAddress['zip'] - Zip Code (must be number)
     */
    public function setDestination(array $destinationAddress) {
        $this->_destination = $destinationAddress;
    }

    /**
     * Method to interact with API server
     * @param <string> XML-string with request
     * @return <XML> responce
     */
    private function _requestAPIServer($query){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->_server);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $query);

        $result = curl_exec($ch);
        curl_close($ch);

        return $result;
    }

    /**
     * Method request USPS API server for shipping rate
     * @return string Server responce in XML format
     */
    public function run() {
        if (!$this->_packageWeight['pounds'] && !$this->_packageWeight['ounces']){
            return array(
                'type'  => 'error',
                'price' => '0',
                'descr' => 'USPS Error: weight not set'
            );
        }

        if ($this->_origination['country'] == $this->_destination['country']) {
            $result = $this->_domesticRates();
        } else {
            $result = $this->_internationalRates();
        }
        return $result;
    }

    /**
     * Domestic Rate API realization
     * @return array
     */
    private function _domesticRates(){
        $result = array();
        
        $query  = 'API=RateV3&XML=';
        $query .= '<RateV3Request USERID="'.$this->_userId .'">';
        $query .= '<Package ID="1">';
        $query .= '<Service>'.$this->_domesticParams['serviceType'].'</Service>';
        $query .= "<ZipOrigination>".$this->_origination['zip']."</ZipOrigination>";
        $query .= "<ZipDestination>".$this->_destination['zip']."</ZipDestination>";
        $query .= "<Pounds>".$this->_packageWeight['pounds']."</Pounds>";
        $query .= "<Ounces>".$this->_packageWeight['ounces']."</Ounces>";
        $query .= "<Size>".$this->_domesticParams['packageSize']."</Size>";
        $query .= "<Machinable>".$this->_domesticParams['machinable']."</Machinable></Package>";
        $query .= "</RateV3Request>";

        $responce = $this->_requestAPIServer($query);

        $xml = simplexml_load_string($responce);
        if (count($xml->Description)) {
            return array(
                'type'  => 'error',
                'price' => '0',
                'descr' => 'USPS Error: '.(string)$xml->Description.' ('.(string)$xml->Source.')'
            );
        }
        foreach ($xml->Package as $package){
            if (count($package->Error)){
                $error = array(
                    'type'  => 'error',
                    'price' => '0',
                    'descr' => 'USPS Error: '.(string)$package->Error->Description
                    );
                array_push($result, $error);
                continue;
            }
            foreach ($package->Postage as $postage){
                $tmp = array (
                    'type' => (string)$postage->MailService,
                    'price'=> (string)($postage->CommercialRate ? $postage->CommercialRate : $postage->Rate)
                    );
                array_push($result, $tmp);
            }
        }
        if (count($result)==1) $result = $result[0];
        
        return $result;
    }

    /**
     * International Rates API realization
     * @return array 
     */
    private function _internationalRates(){
        if (strlen($this->_destination['country']) == 2 ) {
            $this->_destination['country'] = $this->_getCountryFullName($this->_destination['country']);
        }
        $query  = 'API=IntlRate&XML=';
        $query .= '<IntlRateRequest USERID="'. $this->_userId .'">';
        $query .= '<Package ID="1ST">';
        $query .= '<Pounds>'.$this->_packageWeight['pounds'].'</Pounds>';
        $query .= '<Ounces>'.$this->_packageWeight['ounces'].'</Ounces>';
        $query .= '<Machinable>'.$this->_internationalParams['machinable'].'</Machinable>';
        $query .= '<MailType>'.$this->_internationalParams['mailtype'].'</MailType>';
        $query .= '<Country>'.$this->_destination['country'].'</Country>';
        $query .= '</Package>';
        $query .= '</IntlRateRequest>';

        $responce = $this->_requestAPIServer($query);
        $xml = simplexml_load_string($responce);
        if (count($xml->Description)) {
            return array(
                'type'  => 'error',
                'price' => '0',
                'descr' => 'USPS Error: '.(string)$xml->Description.' ('.(string)$xml->Source.')'
            );
        }
        if (count($xml->Package)){
            foreach ($xml->Package as $package){
                if ($package->Error){
                    return array(
                        'type'  => 'error',
                        'price' => '0',
                        'descr' => 'USPS Error: '.(string)$package->Error->Description.' ('.(string)$package->Error->Source.')'
                    );
                }

                if ($package->Service){
                    $result = array();
                    foreach ($package->Service as $service){
                        $tmp = array (
                            'type'  => (string)$service->SvcDescription,
                            'price' => (string)$service->Postage,
                            'descr' => (string)$service->SvcCommitments .' '. (string)$service->MaxDimensions
                        );
                        array_push($result, $tmp);
                    }

                    return $result;
                }
            }
        }
        return $responce;
    }

    /**
     * Method returns html code for config screen
     * @return string
     */
    public function getConfigScreen(){
        $html  = '
        <p><label>User ID: </label><input type="text" name="userid" /></p>
        <p><label>Machinable:</label>
        <select name="machinable">
        <option value="true">TRUE</option>
        <option value="false">FALSE</option>
        </select></p>
        <p style="border-bottom:1px solid black;border-top:1px solid black;text-align:center">
        <label>Domestic Params:</label></p>
        <p><label>Service Type: </label>
        <select name="service-type">
            <option value="FIRST CLASS">FIRST CLASS</option>
            <option value="PRIORITY">PRIORITY</option>
            <option value="PRIORITY COMMERCIAL">PRIORITY COMMERCIAL</option>
            <option value="EXPRESS">EXPRESS</option>
            <option value="EXPRESS COMMERCIAL">EXPRESS COMMERCIAL</option>
            <option value="EXPRESS SH">EXPRESS SH</option>
            <option value="EXPRESS SH COMMERCIAL">EXPRESS SH COMMERCIAL</option>
            <option value="EXPRESS HFP">EXPRESS HFP</option>
            <option value="EXPRESS HFP COMMERCIAL">EXPRESS HFP COMMERCIAL</option>
            <option value="BPM">BPM</option>
            <option value="PARCEL">PARCEL</option>
            <option value="MEDIA">MEDIA</option>
            <option value="LIBRARY">LIBRARY</option>
            <option value="ALL">ALL</option>
            <option value="ONLINE">ONLINE</option>
        </select>
        </p>
        <p><label>Package size:</label>
        <select name="package-size">
        <option value="LARGE">LARGE</option>
        <option value="REGULAR">REGULAR</option>
        <option value="OVERSIZE">OVERSIZE</option>
        </select>
        </p>
        <p  style="border-bottom:1px solid black;border-top:1px solid black;text-align:center">
        <label>International Params:</label></p>
        <p><label>Mail Type:</label>
        <select name="mail-type">
        <option value="Package">Package</option>
        <option value="Postcards or aerogrammes">Postcards or aerogrammes</option>
        <option value="Envelope">Envelope</option>
        </select>
        </p>';
        
        return $html;
    }

    /**
     * Method convert country abbrevation to full name
     * @param string $abbr
     * @return string
     */
    private function _getCountryFullName($abbr){
        $country = array(
            'US' => 'United States', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AD' => 'Andorra',
            'AO' => 'Angola','AI' => 'Anguilla','AG' => 'Antigua and Barbuda','AR' => 'Argentina',
            'AM' => 'Armenia','AW' => 'Aruba','AU' => 'Australia','AT' => 'Austria','AZ' => 'Azerbaijan Republic',
            'BS' => 'Bahamas','BH' => 'Bahrain','BB' => 'Barbados','BE' => 'Belgium',
            'BZ' => 'Belize','BJ' => 'Benin','BM' => 'Bermuda','BT' => 'Bhutan',
            'BO' => 'Bolivia','BA' => 'Bosnia and Herzegovina','BW' => 'Botswana','BR' => 'Brazil',
            'VG' => 'British Virgin Islands','BN' => 'Brunei','BG' => 'Bulgaria','BF' => 'Burkina Faso',
            'BI' => 'Burundi','KH' => 'Cambodia','CA' => 'Canada','CV' => 'Cape Verde',
            'KY' => 'Cayman Islands','TD' => 'Chad','CL' => 'Chile','C2' => 'China',
            'CO' => 'Colombia','KM' => 'Comoros','CK' => 'Cook Islands','CR' => 'Costa Rica',
            'HR' => 'Croatia','CY' => 'Cyprus','CZ' => 'Czech Republic','CD' => 'Democratic Republic of the Congo',
            'DK' => 'Denmark','DJ' => 'Djibouti','DM' => 'Dominica','DO' => 'Dominican Republic',
            'EC' => 'Ecuador','SV' => 'El Salvador','ER' => 'Eritrea','EE' => 'Estonia',
            'ET' => 'Ethiopia','FK' => 'Falkland Islands','FO' => 'Faroe Islands','FM' => 'Federated States of Micronesia',
            'FJ' => 'Fiji','FI' => 'Finland','FR' => 'France','GF' => 'French Guiana',
            'PF' => 'French Polynesia','GA' => 'Gabon Republic','GM' => 'Gambia','DE' => 'Germany',
            'GI' => 'Gibraltar','GR' => 'Greece','GL' => 'Greenland','GD' => 'Grenada',
            'GP' => 'Guadeloupe','GT' => 'Guatemala','GN' => 'Guinea','GW' => 'Guinea Bissau',
            'GY' => 'Guyana','HN' => 'Honduras','HK' => 'Hong Kong','HU' => 'Hungary',
            'IS' => 'Iceland','IN' => 'India','ID' => 'Indonesia','IE' => 'Ireland',
            'IL' => 'Israel','IT' => 'Italy','JM' => 'Jamaica','JP' => 'Japan',
            'JO' => 'Jordan','KZ' => 'Kazakhstan','KE' => 'Kenya','KI' => 'Kiribati',
            'KW' => 'Kuwait','KG' => 'Kyrgyzstan','LA' => 'Laos','LV' => 'Latvia',
            'LS' => 'Lesotho','LI' => 'Liechtenstein','LT' => 'Lithuania','LU' => 'Luxembourg',
            'MG' => 'Madagascar','MW' => 'Malawi','MY' => 'Malaysia','MV' => 'Maldives',
            'ML' => 'Mali','MT' => 'Malta','MH' => 'Marshall Islands','MQ' => 'Martinique',
            'MR' => 'Mauritania','MU' => 'Mauritius','YT' => 'Mayotte','MX' => 'Mexico',
            'MN' => 'Mongolia','MS' => 'Montserrat','MA' => 'Morocco','MZ' => 'Mozambique',
            'NA' => 'Namibia','NR' => 'Nauru','NP' => 'Nepal','NL' => 'Netherlands',
            'AN' => 'Netherlands Antilles','NC' => 'New Caledonia','NZ' => 'New Zealand','NI' => 'Nicaragua',
            'NE' => 'Niger','NU' => 'Niue','NF' => 'Norfolk Island','NO' => 'Norway',
            'OM' => 'Oman','PW' => 'Palau','PA' => 'Panama','PG' => 'Papua New Guinea',
            'PE' => 'Peru','PH' => 'Philippines','PN' => 'Pitcairn Islands','PL' => 'Poland',
            'PT' => 'Portugal','QA' => 'Qatar','CG' => 'Republic of the Congo','RE' => 'Reunion',
            'RO' => 'Romania','RU' => 'Russia','RW' => 'Rwanda','VC' => 'Saint Vincent and the Grenadines',
            'WS' => 'Samoa','SM' => 'San Marino','ST' => 'São Tomé and Príncipe','SA' => 'Saudi Arabia',
            'SN' => 'Senegal','SC' => 'Seychelles','SL' => 'Sierra Leone','SG' => 'Singapore',
            'SK' => 'Slovakia','SI' => 'Slovenia','SB' => 'Solomon Islands','SO' => 'Somalia',
            'ZA' => 'South Africa','KR' => 'South Korea','ES' => 'Spain','LK' => 'Sri Lanka',
            'SH' => 'St. Helena','KN' => 'St. Kitts and Nevis','LC' => 'St. Lucia','PM' => 'St. Pierre and Miquelon',
            'SR' => 'Suriname','SJ' => 'Svalbard and Jan Mayen Islands','SZ' => 'Swaziland','SE' => 'Sweden',
            'CH' => 'Switzerland','TW' => 'Taiwan','TJ' => 'Tajikistan','TZ' => 'Tanzania',
            'TH' => 'Thailand','TG' => 'Togo','TO' => 'Tonga','TT' => 'Trinidad and Tobago',
            'TN' => 'Tunisia','TR' => 'Turkey','TM' => 'Turkmenistan','TC' => 'Turks and Caicos Islands',
            'TV' => 'Tuvalu','UG' => 'Uganda','UA' => 'Ukraine','AE' => 'United Arab Emirates',
            'GB' => 'United Kingdom','UY' => 'Uruguay','VU' => 'Vanuatu','VA' => 'Vatican City State',
            'VE' => 'Venezuela','VN' => 'Vietnam','WF' => 'Wallis and Futuna Islands','YE' => 'Yemen','ZM' => 'Zambia'
        );

        return $country[$abbr];
    }

}